// This file is a part of Funky C++.
// Copyright (C) 2011  Valentin David
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program.  If not, see <http://www.gnu.org/licenses/>.
#ifndef BUS_HH
# define BUS_HH

# include <bitset>
# include <cstring>

# ifdef __SSE__
#  include <xmmintrin.h>
# endif
template <typename T, size_t N>
struct bus {
private:
  T* buffer;
public:
  typedef T sample;
  enum: size_t {
     size = N
  };

  bus(): buffer(new T[N]) {
    for (size_t i = 0; i < N; ++i) {
      buffer[i] = T();
    }
  }
  ~bus() {
    delete[] buffer;
  }

  bus(const bus& other): buffer(new T[N]) {
    for (size_t n = 0; n < N; ++n) {
      buffer[n] = other.buffer[n];
    }
  }

  bus(bus&& other): buffer(other.buffer) {
    other.buffer = NULL;
  }

  explicit bus(const sample* in): buffer(new T[N]) {
    ::memcpy(buffer, in, N*sizeof(sample));
  }

  bus& operator=(const bus& other) {
    for (size_t n = 0; n < N; ++n) {
      buffer[n] = other.buffer[n];
    }
    return *this;
  }

  bus& operator=(const sample* in) {
    for (size_t n = 0; n < N; ++n) {
      buffer[n] = in[n];
    }
    return *this;
  }

  bus& operator+=(const T& v) {
    for (size_t n = 0; n < N; ++n) {
      buffer[n] += v;
    }
    return *this;
  }

  bus& operator*=(const T& v) {
    for (size_t n = 0; n < N; ++n) {
      buffer[n] *= v;
    }
    return *this;
  }

  bus& operator+=(const bus& other) {
    for (size_t n = 0; n < N; ++n) {
      buffer[n] += other.buffer[n];
    }
    return *this;
  }

  bus& operator*=(const bus& other) {
    for (size_t n = 0; n < N; ++n) {
      buffer[n] *= other.buffer[n];
    }
    return *this;
  }

  T& operator[](size_t n) {
    return buffer[n];
  }
  const T& operator[](size_t n) const {
    return buffer[n];
  }
};

# ifdef __SSE__

template <size_t N>
struct bus<float, N> {
private:
  static const size_t bsize = sizeof(float)*N/sizeof(__m128);
  struct resource {
    __m128 buffer[sizeof(float)*N/sizeof(__m128)];
  };
  resource *r;
public:
  typedef float sample;
  enum: size_t {
     size = N
  };

  bus(): r(new resource()) {
    for (size_t i = 0; i < bsize; ++i) {
      r->buffer[i] = _mm_set1_ps(0.);;
    }
  }
  ~bus() {
    delete r;
  }

  bus(const bus& other): r(new resource()) {
    for (size_t n = 0; n < (sizeof(float)*N/sizeof(__m128)); ++n)
      r->buffer[n] = other.r->buffer[n];
  }

  bus(bus&& other): r(other.r) {
    other.r = NULL;
  }

  bus& operator=(const bus& other) {
    for (size_t n = 0; n < (sizeof(float)*N/sizeof(__m128)); ++n)
      r->buffer[n] = other.r->buffer[n];
    return *this;
  }

  bus& operator=(sample const* other) {
    for (size_t n = 0; n < (sizeof(float)*N/sizeof(__m128)); ++n)
      r->buffer[n] = _mm_set_ps(other[n*sizeof(float)/sizeof(__m128)],
                                other[1+n*sizeof(float)/sizeof(__m128)],
                                other[2+n*sizeof(float)/sizeof(__m128)],
                                other[3+n*sizeof(float)/sizeof(__m128)]);
    return *this;
  }

  bus& operator+=(const float& v) {
    __m128 values = _mm_set1_ps(v);
    for (size_t n = 0; n < (sizeof(float)*N/sizeof(__m128)); ++n)
      r->buffer[n] = _mm_add_ps(r->buffer[n], values);
    return *this;
  }

  bus& operator*=(const float& v) {
    __m128 values = _mm_set1_ps(v);
    for (size_t n = 0; n <sizeof(float)*N/sizeof(__m128); ++n)
      r->buffer[n] = _mm_mul_ps(r->buffer[n], values);
    return *this;
  }

  bus& operator+=(const bus& other) {
    for (size_t n = 0; n <sizeof(float)*N/sizeof(__m128); ++n) {
      r->buffer[n] = _mm_add_ps(r->buffer[n], other.r->buffer[n]);
    }
    return *this;
  }

  bus& operator*=(const bus& other) {
    for (size_t n = 0; n <sizeof(float)*N/sizeof(__m128); ++n) {
      r->buffer[n] = _mm_mul_ps(r->buffer[n], other.r->buffer[n]);
    }
    return *this;
  }

  float& operator[](size_t n) {
    return reinterpret_cast<float*>(&(r->buffer))[n];
  }
  const float& operator[](size_t n) const {
    return reinterpret_cast<const float*>(&(r->buffer))[n];
  }
};

#endif

template <typename T, size_t N, typename U>
bus<T, N>
operator*(const U& a, const bus<T, N>& b) {
  bus<T, N> ret;
  for (size_t n = 0; n < N; ++n) {
    ret = a * b[n];
  }
  return ret;
}

template <typename T, size_t N, typename U>
bus<T, N>
operator*(const U& a, bus<T, N>&& b) {
  bus<T, N> ret = std::move(b);
  for (size_t n = 0; n < N; ++n) {
    ret = a * b[n];
  }
  return std::move(ret);
}

template <typename T, size_t N, typename U>
bus<T, N>
operator*(const bus<T, N>& a, const U& b) {
  bus<T, N> ret;
  for (size_t n = 0; n < N; ++n) {
    ret = a[n] * b;
  }
  return std::move(ret);
}

template <typename T, size_t N, typename U>
bus<T, N>
operator*(bus<T, N>&& a, const U& b) {
  bus<T, N> ret = std::move(a);;
  for (size_t n = 0; n < N; ++n) {
    ret *= b;
  }
  return std::move(ret);
}

template <typename T, typename U, size_t N,
	  typename Ret = decltype(std::declval<T>() * std::declval<U>())>
bus<Ret, N>
operator+(const bus<T, N>& a, const bus<T, N>& b) {
  bus<Ret, N> ret;
  for (size_t n = 0; n < N; ++n) {
    ret = a[n] + b[n];
  }
  return std::move(ret);
}

template <typename T, size_t N>
bus<T, N>
operator+(bus<T, N>&& a, const bus<T, N>& b) {
  bus<T, N> ret = std::move(a);
  ret+=b;
  return std::move(ret);
}

template <typename T, size_t N>
bus<T, N>
operator+(bus<T, N>&& a, bus<T, N>&& b) {
  bus<T, N> ret = std::move(a);
  ret+=b;
  return std::move(ret);
}

template <typename T, size_t N>
bus<T, N>
operator*(const bus<T, N>& a, const bus<T, N>& b) {
  bus<T, N> ret{a};
  ret *= b;
  return std::move(ret);
}

template <typename T, size_t N>
bus<T, N>
operator+(const bus<T, N>& a, bus<T, N>&& b) {
  bus<T, N> ret = std::move(b);
  ret+=a;
  return std::move(ret);
}

template <typename T, size_t N>
bus<T, N>
operator+(const bus<T, N>& a, const bus<T, N>& b) {
  bus<T, N> ret{a};
  ret+=b;
  return std::move(ret);
}

template <typename T, size_t N>
bus<T, N>
operator*(bus<T, N>&& a, const bus<T, N>& b) {
  bus<T, N> ret = std::move(a);
  ret *= b;
  return std::move(ret);
}

template <typename T, size_t N>
bus<T, N>
operator*(bus<T, N>&& a, bus<T, N>&& b) {
  bus<T, N> ret = std::move(a);
  ret *= b;
  return std::move(ret);
}

template <typename T, size_t N>
bus<T, N>
operator*(const bus<T, N>& a, bus<T, N>&& b) {
  bus<T, N> ret = std::move(b);
  ret *= a;
  return std::move(ret);
}

template <typename T, size_t N>
bus<T, N>
operator*(const std::bitset<N>& b, T a) {
  bus<T, N> ret;
  for (size_t n = 0; n < N; ++n)
    ret[n]=b.test(n)?a:T();
  return std::move(ret);
}

template <typename T, size_t N>
bus<T, N>
operator*(T a, const std::bitset<N>& b) {
  bus<T, N> ret;
  for (size_t n = 0; n < N; ++n)
    ret[n]=b.test(n)?a:T();
       return std::move(ret);
}

template <typename T, size_t N>
bus<T, N>
operator*(const std::bitset<N>& b, const bus<T, N>& a) {
  bus<T, N> ret;
  for (size_t n = 0; n < N; ++n)
    ret[n]=b.test(n)?a[n]:T();
       return std::move(ret);
}

template <typename T, size_t N>
bus<T, N>
operator*(const std::bitset<N>& b, bus<T, N>&& a) {
  bus<T, N> ret = std::move(a);
  for (size_t n = 0; n < N; ++n)
    ret[n]=b.test(n)?a[n]:T();
       return std::move(ret);
}

template <typename T, size_t N>
bus<T, N>
  operator*(bus<T, N>&& a, const std::bitset<N>& b) {
  bus<T, N> ret = std::move(a);
  for (size_t n = 0; n < N; ++n)
    ret[n]=b.test(n)?a[n]:T();
       return std::move(ret);
}

template <typename T, size_t N>
bus<T, N>
operator*(const bus<T, N>& a, const std::bitset<N>& b) {
  bus<T, N> ret;
  for (size_t n = 0; n < N; ++n)
    ret[n]=b.test(n)?a[n]:T();
       return std::move(ret);
}

#endif
